extern crate rand;
extern crate termion;

mod colorconv;

use colorconv::{UpdateColor, HSV, RGB};

use rand::{thread_rng, Rng};

use std::io::stdout;
use std::str::FromStr;

use termion::input::TermRead;
use termion::raw::IntoRawMode;
use termion::{async_stdin, clear, color, cursor, style};

/// Menu at the top
const CONTROLS: &'static str = "True Color Hex Generator \n\n\r\
          r/g/b/h/s/v: select attribute | ↑/↓ : increment\n\r\
          x: reset | z: zero all | q: quit | w: deselect\n";

/// State for the main loop
struct App {
    focus: String, // position in the menu
    color: RGB,
    origin: RGB,
    hsv: HSV,
}

/// Maybe this mirrors OOP too much
impl App {
    fn new(color: RGB) -> Self {
        let _dimensions = termion::terminal_size().unwrap();
        let app = App {
            focus: "main".to_owned(),
            color: color,
            origin: color,
            hsv: HSV::from_rgb(color),
        };
        app
    }

    /// Set the initial color
    fn term_color(&self) -> color::Rgb {
        color::Rgb(self.color.red(), self.color.green(), self.color.blue())
    }

    /// Reset to initial color
    fn reset(&mut self) {
        self.color = self.origin;
        self.hsv = HSV::from_rgb(self.color);
    }

    /// Set all values to 0
    fn clear(&mut self) {
        self.color = RGB::new(0, 0, 0);
        self.hsv = HSV::from_rgb(self.color);
    }

    /// Highlight the selected field
    fn highlight(&self, field: &str) -> String {
        if field == self.focus {
            format!("{}{}:{}", style::Invert, field, style::Reset)
        } else {
            format!("{}:", field)
        }
    }

    /// Formats and prints everything
    fn update_display(&self) -> String {
        let output = format!(
            "{}{}Current color is: #{}{} █████ {}\n\r\
            {} {:03}\t{} {:>6.2}°\n\r\
            {} {:03}\t{} {:>6.2}%\n\r\
            {} {:03}\t{} {:>6.2}%\n\r\
        ",
            cursor::Goto(1, 6),
            color::Fg(color::Reset),
            self.color.hex(),
            color::Fg(self.term_color()),
            color::Fg(color::Reset),
            self.highlight("r"),
            self.color.red(),
            self.highlight("h"),
            self.hsv.hue(),
            self.highlight("g"),
            self.color.green(),
            self.highlight("s"),
            self.hsv.saturation() * 100.0,
            self.highlight("b"),
            self.color.blue(),
            self.highlight("v"),
            self.hsv.value() * 100.0,
        );
        output
    }

    /// The main loop for the UI
    fn app_loop(&mut self) {
        println!(
            "{}{}{}{}",
            clear::All,
            cursor::Goto(1, 1),
            cursor::Hide,
            CONTROLS
        );
        let mut stdin = async_stdin().keys();
        let mut _stdout = stdout().into_raw_mode().unwrap();

        use termion::event::Key::*;
        loop {
            let input = stdin.next();
            println!("{}", self.update_display());
            if let Some(Ok(key)) = input {
                match key {
                    Char('q') => {
                        println!("{}", termion::cursor::Show);
                        break;
                    }
                    Char('r') => self.focus = "r".to_owned(),
                    Char('g') => self.focus = "g".to_owned(),
                    Char('b') => self.focus = "b".to_owned(),
                    Char('h') => self.focus = "h".to_owned(),
                    Char('s') => self.focus = "s".to_owned(),
                    Char('v') => self.focus = "v".to_owned(),
                    Char('w') => self.focus = "m".to_owned(),
                    Char('x') => self.reset(),
                    Char('z') => self.clear(),
                    termion::event::Key::Up | Char('A') => self.increment_color(1),
                    termion::event::Key::Down | Char('B') => self.increment_color(-1),
                    _ => {}
                };
            }
        }
    }

    /// Match the "focused" attribute. Might refactor the idea of a focus later
    fn increment_color(&mut self, inc: i32) {
        let hsv_original = self.hsv.clone();
        let rgb_original = self.color.clone();

        match self.focus.clone().as_str() {
            "r" => self.color.red.increment(inc as i16),
            "g" => self.color.green.increment(inc as i16),
            "b" => self.color.blue.increment(inc as i16),
            "h" => self.hsv.hue.increment(inc as f32 * 0.5),
            "s" => self.hsv.saturation.increment(inc as f32 * 0.005),
            "v" => self.hsv.value.increment(inc as f32 * 0.005),
            _ => {}
        };

        if hsv_original != self.hsv {
            self.color = RGB::from_hsv(self.hsv);
        } else if rgb_original != self.color {
            self.hsv = HSV::from_rgb(self.color);
        }
    }
}

/// Main loop
fn main() {
    unit_tests();
    let args: Vec<String> = std::env::args().collect();
    let mut colorstring = "000000";
    if args.len() > 1 {
        colorstring = &args[1];
    }
    let current_color = RGB::from_str(colorstring).unwrap();
    let mut app = App::new(current_color);
    app.app_loop();
}

/// Assert that rgb and hsv formats are interchangeable without drift
fn unit_tests() {
    let mut rng = thread_rng();

    let mut failures = 0;
    for _i in 0..100 {
        let r: u8 = rng.gen_range(0, 255);
        let g: u8 = rng.gen_range(0, 255);
        let b: u8 = rng.gen_range(0, 255);

        println!("starting with r: {} g: {} b: {}", r, g, b);
        let start = RGB::new(r, g, b);
        let middle = HSV::from_rgb(start);
        let end = RGB::from_hsv(middle);
        let four = HSV::from_rgb(end);
        let a = start;
        let b = end;
        println!("{} == {}: \n {}\n\n", a, b, a == b);
        if start != end || four != middle {
            failures += 1;
        }
    }
    assert!(failures == 0);
}
